# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Gitlab::BackgroundMigration::BackfillMissingVulnerabilityDismissalDetails, schema: 20230712145557, feature_category: :vulnerability_management do # rubocop:disable Layout/LineLength
  let(:projects_table) { table(:projects) }
  let(:namespaces_table) { table(:namespaces) }
  let(:users_table) { table(:users) }
  let(:vulnerabilities_table) { table(:vulnerabilities) }
  let(:vsts_table) { table(:vulnerability_state_transitions) }

  let!(:user) { users_table.create!(username: 'john_doe', email: 'johndoe@gitlab.com', projects_limit: 10) }
  let!(:user2) { users_table.create!(username: 'jane_doe', email: 'janedoe@gitlab.com', projects_limit: 10) }
  let!(:namespace) { namespaces_table.create!(name: 'namespace', path: 'namespace-path-1') }
  let!(:project) do
    projects_table
    .create!(
      name: 'project1',
      path: 'path1',
      namespace_id: namespace.id,
      project_namespace_id: namespace.id,
      visibility_level: 0
    )
  end

  subject(:perform_migration) do
    described_class.new(start_id: vulnerabilities_table.minimum(:id),
      end_id: vulnerabilities_table.maximum(:id),
      batch_table: :vulnerabilities,
      batch_column: :id,
      sub_batch_size: 2,
      pause_ms: 0,
      connection: vulnerabilities_table.connection)
                                       .perform
  end

  context 'when the vulnerability is not correctly dismissed' do
    let!(:mangled_dismissed_vulnerability) { create_vulnerability(dismissed_at: nil, dismissed_by_id: nil) }
    let!(:mangled_dismissed_vulnerability_non_dismissal_vst) do
      create_vst(vulnerability_id: mangled_dismissed_vulnerability.id, to_state: 3)
    end

    let!(:mangled_dismissed_vulnerability_older_dismissal_vst) do
      create_vst(
        vulnerability_id: mangled_dismissed_vulnerability.id,
        to_state: 2,
        created_at: Time.zone.now - 1.day
      )
    end

    let!(:mangled_dismissed_vulnerability_dismissal_vst) do
      create_vst(vulnerability_id: mangled_dismissed_vulnerability.id, author_id: user.id)
    end

    let!(:incorrect_attributes) { mangled_dismissed_vulnerability.attributes }

    let!(:correct_attributes) do
      mangled_dismissed_vulnerability.attributes.merge({
        "dismissed_at" => mangled_dismissed_vulnerability_dismissal_vst.created_at,
        "dismissed_by_id" => mangled_dismissed_vulnerability_dismissal_vst.author_id
      })
    end

    it 'applies the applicable state transition information to malformed state transitions', :freeze_time do
      expect { perform_migration }.to change {
                                        mangled_dismissed_vulnerability.reload.attributes
                                      }.to(correct_attributes)
    end

    it 'logs a succesful outcome' do
      expect(::Gitlab::BackgroundMigration::Logger).to receive(:info).with(
        migrator: "Gitlab::BackgroundMigration::BackfillMissingVulnerabilityDismissalDetails",
        message: 'Vulnerability dismissal information restored.',
        vulnerability_id: mangled_dismissed_vulnerability.id
      )

      perform_migration
    end

    context 'and does not have a corresponding vst' do
      let!(:mangled_dismissed_vulnerability_with_no_vst) do
        create_vulnerability(dismissed_at: nil, dismissed_by_id: nil)
      end

      it 'does not modify the vulnerability' do
        expect { perform_migration }.not_to change { mangled_dismissed_vulnerability_with_no_vst.reload.attributes }
      end

      it 'logs an appropriate warning' do
        expect(::Gitlab::BackgroundMigration::Logger).to receive(:warn).with(
          migrator: "Gitlab::BackgroundMigration::BackfillMissingVulnerabilityDismissalDetails",
          message: 'Invalid dismissed vulnerability lacks a state transition to restore from.',
          vulnerability_id: mangled_dismissed_vulnerability_with_no_vst.id
        )

        perform_migration
      end
    end
  end

  context 'when the vulnerability is correctly dismissed' do
    let!(:correctly_dismissed_vulnerability) { create_vulnerability }
    let!(:correct_dismissed_vulnerability_vst) { create_vst(vulnerability_id: correctly_dismissed_vulnerability.id) }

    it 'does not modify correct vulnerabilities' do
      expect { perform_migration }.not_to change { correctly_dismissed_vulnerability.reload.attributes }
    end
  end

  private

  def create_vulnerability(params = {})
    vulnerabilities_table.create!({
      project_id: project.id,
      author_id: user.id,
      title: 'test',
      severity: 1,
      confidence: 1,
      report_type: 1,
      state: 2,
      dismissed_at: Time.zone.now,
      dismissed_by_id: user.id,
      detected_at: Time.zone.now
    }.merge(params))
  end

  def create_vst(params = {})
    vsts_table.create!({
      author_id: user2.id,
      from_state: 1,
      to_state: 2
    }.merge(params))
  end
end
